<template>
  <view class="u-cascader-asymmetric" :style="[customStyle]">
    <u-popup
      :blur="blur"
      :maskCloseAble="maskCloseAble"
      mode="bottom"
      :popup="false"
      v-model="popupValue"
      length="auto"
      :safeAreaInsetBottom="safeAreaInsetBottom"
      @close="close"
      :z-index="uZIndex"
    >
      <view class="u-cascader-asymmetric">
        <view class="u-cascader-asymmetric__header" @touchmove.stop.prevent="">
          <view
            class="u-cascader-asymmetric__header__cancel u-cascader-asymmetric__header__btn"
            :style="{ color: cancelColor }"
            hover-class="u-hover-class"
            :hover-stay-time="150"
            @tap="getResult('cancel')"
          >
            {{ cancelText }}
          </view>
          <view class="u-cascader-asymmetric__header__title">
            {{ title }}
          </view>
          <view
            class="u-cascader-asymmetric__header__confirm u-cascader-asymmetric__header__btn"
            :style="{ color: confirmColor }"
            hover-class="u-hover-class"
            :hover-stay-time="150"
            @touchmove.stop=""
            @tap.stop="getResult('confirm')"
          >
            {{ confirmText }}
          </view>
        </view>
        <view class="u-cascader-asymmetric__body">
          <view class="u-cascader-asymmetric__body__selected">
            <scroll-view scroll-x :scrollLeft="scrollLeft" class="tab-scroll-view">
              <view
                v-for="(select, index) in selectValue"
                :key="index"
                class="u-cascader-asymmetric__body__selected-item"
                :class="'selected-item-' + index"
                @click="() => handleChangePanel(index)"
              >
                {{ select[fieldNamesComplete.label] }}
              </view>
            </scroll-view>
            <view class="u-cascader-asymmetric__body__selected-bar" :style="[barStyle]"></view>
          </view>
          <swiper
            class="u-cascader-asymmetric__body__swiper"
            :disable-touch="true"
            :duration="200"
            :current="currentPanel"
          >
            <swiper-item v-for="(panelItem, panelIndex) in columnData">
              <scroll-view scroll-y class="swiper-item u-cascader-asymmetric__body__picker-view">
                <view
                  v-for="(item, index) in panelItem"
                  :key="index"
                  class="u-cascader-asymmetric__body__picker-view__item"
                  :class="{
                    'u-cascader-asymmetric__body__picker-view__item-selected':
                      selectValue[panelIndex] &&
                      selectValue[panelIndex][fieldNamesComplete.value] ===
                        item[fieldNamesComplete.value],
                  }"
                  @click="() => handleSelectItem(item, panelIndex, index)"
                >
                  <view class="u-label">{{ item[fieldNamesComplete.label] }}</view>
                  <u-icon
                    class="u-icon"
                    v-if="
                      selectValue[panelIndex] &&
                      selectValue[panelIndex][fieldNamesComplete.value] ===
                        item[fieldNamesComplete.value]
                    "
                    name="checkbox-mark"
                    color="#2979ff"
                    size="28"
                  />
                </view>
              </scroll-view>
            </swiper-item>
          </swiper>
        </view>
      </view>
    </u-popup>
  </view>
</template>

<script>
  import mpConfig from '../../libs/mixin/mp-vertual-node.js';
  /**
   * cascader-asymmetric 非对称级联选择器
   * @description 非对称级联选择器，可用于非对称的数据的选择与展示
   * @property {String} mode 模式选择，
   * @property {Array} list 列数据，数组形式
   * @property {Boolean} v-model 布尔值变量，用于控制选择器的弹出与收起
   * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
   * @property {String} cancel-color 取消按钮的颜色（默认#606266）
   * @property {String} confirm-color 确认按钮的颜色(默认#2979ff)
   * @property {String} confirm-text 确认按钮的文字
   * @property {String} cancel-text 取消按钮的文字
   * @property {String} default-value 提供的默认选中的下标，见官网说明
   * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
   * @property {String Number} z-index 弹出时的z-index值(默认10075)
   * @property {String} value-name 自定义list数据的value属性名 1.3.6
   * @property {String} label-name 自定义list数据的label属性名 1.3.6
   * @property {String} child-name 自定义list数据的children属性名，只对多列联动模式有效 1.3.7
   * @event {Function} confirm 点击确定按钮，返回当前选择的值
   * @example <u-cascader-asymmetric v-model="show" :list="list"></u-cascader-asymmetric>
   */

  export default {
    name: 'u-cascader-asymmetric',
    options: mpConfig,
    emits: ['update:modelValue', 'update:pickValue', 'input', 'confirm'],
    props: {
      // 通过双向绑定控制组件的弹出与收起
      value: {
        type: Boolean,
        default: false,
      },
      modelValue: {
        type: Boolean,
        default: false,
      },
      // 列数据
      list: {
        type: Array,
        default() {
          return [];
        },
      },
      // 是否显示边框
      border: {
        type: Boolean,
        default: true,
      },
      // "取消"按钮的颜色
      cancelColor: {
        type: String,
        default: '#606266',
      },
      // "确定"按钮的颜色
      confirmColor: {
        type: String,
        default: '#2979ff',
      },
      // 弹出的z-index值
      zIndex: {
        type: [String, Number],
        default: 0,
      },
      safeAreaInsetBottom: {
        type: Boolean,
        default: false,
      },
      // 是否允许通过点击遮罩关闭Picker
      maskCloseAble: {
        type: Boolean,
        default: true,
      },
      // 提供的默认选中的下标
      defaultValue: {
        type: Array,
        default() {
          return [0];
        },
      },
      // 模式选择
      mode: {
        type: String,
        default: 'cascader',
      },
      // 顶部标题
      title: {
        type: String,
        default: '',
      },
      // 取消按钮的文字
      cancelText: {
        type: String,
        default: '取消',
      },
      // 确认按钮的文字
      confirmText: {
        type: String,
        default: '确认',
      },
      // 遮罩的模糊度
      blur: {
        type: [Number, String],
        default: 0,
      },
      // 用于回显的value
      pickValue: {
        type: Array,
        default() {
          return [];
        },
      },
      fieldNames: {
        type: Object,
        default: () => {
          return {
            children: 'children',
            label: 'label',
            value: 'value',
          };
        },
      },
    },
    data() {
      return {
        popupValue: false,
        // 用于列改变时，保存当前的索引，下一次变化时比较得出是哪一列发生了变化
        defaultSelector: [0],
        // 每次队列发生变化时，保存选择的结果
        selectValue: [{ label: '请选择' }],
        // 当前滑块
        currentPanel: 0,
        // Tab滑块滚动位置
        scrollLeft: 0,
        // Tab容器宽度
        tabScrollWidth: 0,
        // 当前滑块位置
        currentPanelTab: {},
      };
    },
    watch: {
      // 弹起的时候，重新初始化所有数据
      value: {
        immediate: true,
        handler(val) {
          if (val) setTimeout(() => this.init(), 10);
          this.popupValue = val;
        },
      },
      modelValue: {
        immediate: true,
        handler(val) {
          if (val) setTimeout(() => this.init(), 10);
          this.popupValue = val;
        },
      },
      pickValue: {
        immediate: true,
        handler() {
          this.init();
          this.updateSelectedValue();
        },
      },
      list: {
        immediate: true,
        handler() {
          this.init();
        },
      },
    },
    computed: {
      fieldNamesComplete() {
        const { children, label, value } = this.fieldNames;
        return {
          children: children ?? 'children',
          label: label ?? 'label',
          value: value ?? 'value',
        };
      },
      keyValueMap() {
        const keyMap = {};
        // 获取映射字段
        const { children: childrenKey, value: valueKey } = this.fieldNamesComplete;
        const recursion = (list, parent, callback) => {
          list.forEach((item) => {
            callback(item, parent);
            if (item[childrenKey] && item[childrenKey].length > 0) {
              recursion(item[childrenKey], item, callback);
            }
          });
        };
        recursion(this.list, null, (item, parent) => {
          keyMap[item[valueKey]] = {
            ...item,
            parent,
          };
        });
        return keyMap;
      },
      uZIndex() {
        // 如果用户有传递z-index值，优先使用
        return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
      },
      // list的数据
      columnData() {
        const { children: childrenKey } = this.fieldNamesComplete;
        const result = [this.list];
        this.selectValue.forEach((item) => {
          if (item[childrenKey] && item[childrenKey].length > 0) {
            result.push(item[childrenKey]);
          }
        });
        return result;
      },
      barStyle() {
        const { left, width, fix } = this.currentPanelTab;
        const PADDING = 20;
        const TAB_WIDTH = 20;
        return {
          transform: `translate(${left - fix - PADDING - TAB_WIDTH / 2 + width / 2}px, 0)`,
        };
      },
    },
    methods: {
      init() {
        this.setColumnData();
        this.$nextTick(() => {
          this.getTabScrollWidth();
        });
      },
      getTabScrollWidth() {
        if (!uni.createSelectorQuery()) {
          return;
        }
        const tabRect = uni.createSelectorQuery().select('.tab-scroll-view').boundingClientRect();
        // 获取滚动容器宽度
        tabRect &&
          tabRect.exec((ret) => {
            const [info] = ret;
            this.tabScrollWidth = info?.width || 0;
          });
      },
      getCurrentPanelRect(index) {
        // 获取当前滚动值的信息
        uni
          .createSelectorQuery()
          .select('.selected-item-' + index)
          .boundingClientRect()
          .exec((ret) => {
            const [info] = ret;
            if (!info) {
              return;
            }
            let fix = 0;
            if (this.tabScrollWidth && info.left - this.tabScrollWidth > 0) {
              fix = info.left - this.tabScrollWidth + info.width / 2;
            }
            this.currentPanelTab = {
              width: info.width,
              left: info.left,
              fix,
            };
          });
      },
      handleSelectItem(select, panelIndex, index) {
        const { children: childrenKey, value: valueKey, label: labelKey } = this.fieldNamesComplete;
        this.selectValue.splice(panelIndex, this.selectValue.length, select);
        if (select[childrenKey] && select[childrenKey].length > 0) {
          this.selectValue.splice(panelIndex + 1, 0, { [labelKey]: '请选择' });
          this.$nextTick(() => {
            // 点击时设置一个比较大的滚动值
            this.scrollLeft = this.scrollLeft + 999;
            this.currentPanel = panelIndex + 1;
            this.getCurrentPanelRect(panelIndex + 1);
          });
        } else {
          this.$nextTick(() => {
            this.scrollLeft = this.scrollLeft + 1;
            this.$nextTick(() => {
              this.getCurrentPanelRect(panelIndex);
            });
          });
        }
      },
      handleChangePanel(index) {
        this.currentPanel = index;
        this.getCurrentPanelRect(index);
        // 获取滚动位置
        let _scrollLeft = 0;
        uni
          .createSelectorQuery()
          .select('.tab-scroll-view')
          .scrollOffset()
          .exec((offsetInfo) => {
            const [info] = offsetInfo;
            _scrollLeft = info.scrollLeft;
          });
        const { width, left } = this.currentPanelTab;
        // 将选中的Tab居中
        this.scrollLeft = _scrollLeft - ((this.tabScrollWidth - width) / 2 - left);
        // 更新当前Tab标识位置
        this.$nextTick(() => {
          this.getCurrentPanelRect(index);
        });
      },
      // 获取需要展示在picker中的列数据
      setColumnData() {
        const { children: childrenKey, label: labelKey, value: valueKey } = this.fieldNamesComplete;
        let data = [];
        // 如果选中了数据，回显处理
        // 如果是树形
        if (this.mode === 'tree') {
          // 没有数据
          if (!this.pickValue || !this.keyValueMap || !this.keyValueMap[this.pickValue]) {
            this.currentPanel = 0;
            this.selectValue = [{ [labelKey]: '请选择' }];
          } else {
            // 有数据，从子节点向上追溯回显
            let _currentPanel = 0;
            const _selectValue = [];
            let leaf = this.keyValueMap[this.pickValue];
            while (!!leaf) {
              _selectValue.unshift(this.$u.deepClone(leaf));
              // 使用keyValueMap中的信息
              leaf = leaf.parent ? this.keyValueMap[leaf.parent[valueKey]] : null;
            }
            this.currentPanel = _selectValue.length - 1;
            this.selectValue = _selectValue;
          }
        } else if (this.pickValue && this.pickValue.length > 0) {
          let currentList = this.list;
          let _currentPanel = 0;
          const _selectValue = [];
          this.pickValue.forEach((key, index) => {
            // 找到匹配的项
            const currentItem = currentList.find((item) => item[valueKey] === key);
            if (currentItem) {
              _selectValue.push(currentItem);
              // 将当前list置为下一层
              currentList = currentItem[childrenKey];
              _currentPanel = index;
            } else {
              return;
            }
          });
          // 如果还有下一层
          if (currentList && currentList[childrenKey] && currentList[childrenKey].length > 0) {
            _selectValue.push({ [labelKey]: '请选择' });
            _currentPanel++;
          }
          this.currentPanel = _currentPanel;
          this.selectValue = _selectValue;
        } else {
          this.currentPanel = 0;
          this.selectValue = [{ [labelKey]: '请选择' }];
        }
        this.$nextTick(() => {
          this.getCurrentPanelRect(this.currentPanel);
        });
      },
      close() {
        this.$emit('input', false);
        this.$emit('update:modelValue', false);
      },
      // 点击确定或者取消
      getResult(event = null) {
        const { value: valueKey } = this.fieldNamesComplete;
        if (event == 'confirm') {
          const selectArray = this.selectValue.map((item) => item[valueKey]);
          this.$emit('update:pickValue', selectArray);
          this.updateSelectedValue();
        }
        if (event) {
          this.$nextTick(() => {
            this.$emit(event, this.selectValue);
          });
        }
        this.close();
      },
      updateSelectedValue() {
        this.$emit('updateSelected', this.selectValue);
      },
      selectHandler() {
        this.$emit('click');
      },
    },
  };
</script>

<style scoped lang="scss">
  @import '../../libs/css/style.components.scss';

  .u-cascader-asymmetric {
    &__action {
      position: relative;
      line-height: $u-form-item-height;
      height: $u-form-item-height;

      &__icon {
        position: absolute;
        right: 20rpx;
        top: 50%;
        transition: transform 0.4s;
        transform: translateY(-50%);
        z-index: 1;

        &--reverse {
          transform: rotate(-180deg) translateY(50%);
        }
      }
    }

    &--border {
      border-radius: 6rpx;
      border-radius: 4px;
      border: 1px solid $u-form-item-border-color;
    }

    &__header {
      @include vue-flex;
      align-items: center;
      justify-content: space-between;
      height: 80rpx;
      padding: 0 40rpx;
    }

    &__body {
      width: 100%;
      height: 500rpx;
      overflow: hidden;
      background-color: #fff;

      &__selected {
        position: relative;
        white-space: nowrap;
        margin: 0 40rpx;
        :deep() {
          .uni-scroll-view-content {
            @include vue-flex;
            touch-action: none;

            .u-cascader-asymmetric__body__selected-item {
              margin-right: 20rpx;
            }
          }
        }
        &-bar {
          position: absolute;
          bottom: 0;
          left: 0;
          width: 40rpx;
          height: 8rpx;
          border-radius: 8rpx;
          background-color: $u-type-primary;
          transition-duration: 0.5s;
        }
      }

      &__swiper {
        height: calc(100% - 35px);
      }

      &__picker-view {
        height: 100%;
        box-sizing: border-box;
        padding: 0 40rpx;

        &__item {
          @include vue-flex;
          align-items: center;
          justify-content: space-between;
          font-size: 28rpx;
          color: $u-main-color;
          &-selected {
            font-weight: bold;
            color: $u-type-primary;
          }
        }
      }
    }
  }
</style>
